﻿using Autofac;
using Domain.Models;
using Infrastructure.Data;
using Infrastructure.IService;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace Infrastructure.Services
{
    public class Repository<T> : IRepository<T> where T : BaseEntity
    {
        private readonly ILifetimeScope lifetimeScope;

        public Repository(ILifetimeScope lifetimeScope)
        {
            this.lifetimeScope = lifetimeScope ?? throw new ArgumentNullException(nameof(lifetimeScope));
        }

        public async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
        {
            using(var context = lifetimeScope.Resolve<DataContext>())
            {
                await context.Set<T>().AddAsync(entity, cancellationToken);
                await context.SaveChangesAsync(cancellationToken);

                return entity;
            }
        }

        public async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<DataContext>())
            {
                return predicate != null ?
                    await context.Set<T>().FirstOrDefaultAsync(predicate, cancellationToken) :
                    await context.Set<T>().FirstOrDefaultAsync(cancellationToken);
            }
        }

        public async Task RemoveAsync(T entity, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<DataContext>())
            {
                context.Set<T>().Remove(entity);
                await context.SaveChangesAsync(cancellationToken);
            }
        }

        public async Task<T> SingleOrDefaultAsync(Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<DataContext>())
            {
                return predicate != null ?
                    await context.Set<T>().SingleOrDefaultAsync(predicate, cancellationToken) :
                    await context.Set<T>().SingleOrDefaultAsync(cancellationToken);
            }
        }

        public async Task<IReadOnlyCollection<T>> ToListAsync(Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default)
        {
            using(var context = lifetimeScope.Resolve<DataContext>())
            {
                var result = predicate != null ?
                    await context.Set<T>().Where(predicate).ToListAsync(cancellationToken) :
                    await context.Set<T>().ToListAsync(cancellationToken);

                return result.AsReadOnly();
            }
        }

        public async Task<IReadOnlyCollection<T>> ToListPageAsync(Expression<Func<T, bool>> predicate = null, int size = 10, int index = 1, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<DataContext>())
            {
                var query = predicate != null ?
                    context.Set<T>().Where(predicate) :
                    context.Set<T>().AsQueryable();

                var list = await query.Skip((index - 1) * size)
                    .Take(size)
                    .ToListAsync(cancellationToken);

                return list.AsReadOnly();
            }
        }

        public async Task ModifyAsync(T entity, CancellationToken cancellationToken = default)
        {
            using (var context = lifetimeScope.Resolve<DataContext>())
            {
                context.Entry(entity).State = EntityState.Modified;
                await context.SaveChangesAsync(cancellationToken);
            }
        }
    }
}
